
#include "config.h"

#include <sys/types.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <ctype.h>
#include <errno.h>
#include <fcntl.h>
#include <time.h>
#include <netdb.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/uio.h>
#define DEFAULT_TEXT_DOMAIN	"sudo"
#include "sudo_gettext.h"	/* must be included before sudo_compat.h */

#include "sudo_compat.h"
#include "sudo_fatal.h"
#include "sudo_plugin.h"
#include "sudo_debug.h"
#include "sudo_conf.h"
#include "sudo_util.h"

#ifdef HAVE_STDBOOL_H
# include <stdbool.h>
#else
# include "compat/stdbool.h"
#endif /* HAVE_STDBOOL_H */
#include "sudo_queue.h"
#include "sudo_lbuf.h"


#ifndef HAVE_GETADDRINFO
# include "compat/getaddrinfo.h"
#endif

#include <langinfo.h>
#include <syslog.h>
#include <wait.h>
#include <pwd.h>
#include "sudoers.h"

void do_syslog(int, char *);
bool do_logfile(const char *);
bool send_mail(const char *fmt, ...);
bool should_mail(int);
void mysyslog(int, const char *, ...);
char *new_logline(const char *, const char *);

extern bool sudoers_setlocale(int newlocale, int *prevlocale);
extern bool set_perms(int perm);
extern void openlog (const char *__ident, int __option, int __facility);
extern void closelog (void);
extern bool restore_perms(void);
extern int linux_audit_command(char *argv[], int result);
extern char *get_timestr(time_t tstamp, int log_year);
extern  void closefrom_nodebug(int lowfd);
extern void __attribute__((__noreturn__)) exec_mailer(int *pfd);

enum def_tuple {
	never,
	once,
	always,
	any,
	all,
	digest_only,
	global,
	ppid,
	tty,
	kernel
};

/*
 * Four types of defaults: strings, integers, and flags.
 * Also, T_INT, T_TIMESPEC or T_STR may be ANDed with T_BOOL to indicate that
 * a value is not required.  Flags are boolean by nature...
 */
#undef T_INT
#define T_INT		0x001
#undef T_UINT
#define T_UINT		0x002
#undef T_STR
#define T_STR		0x003
#undef T_FLAG
#define T_FLAG		0x004
#undef T_MODE
#define T_MODE		0x005
#undef T_LIST
#define T_LIST		0x006
#undef T_LOGFAC
#define T_LOGFAC	0x007
#undef T_LOGPRI
#define T_LOGPRI	0x008
#undef T_TUPLE
#define T_TUPLE		0x009
#undef T_TIMESPEC
#define T_TIMESPEC	0x010
#undef T_TIMEOUT
#define T_TIMEOUT	0x020
#undef T_MASK
#define T_MASK		0x0FF
#undef T_BOOL
#define T_BOOL		0x100
#undef T_PATH
#define T_PATH		0x200

/* Mapping of tuple string value to enum def_tuple. */
struct def_values {
    char *sval;		/* string value */
    enum def_tuple nval;/* numeric value */
};

struct list_member {
    SLIST_ENTRY(list_member) entries;
    char *value;
};

SLIST_HEAD(list_members, list_member);

union sudo_defs_val {
    int flag;
    int ival;
    unsigned int uival;
    enum def_tuple tuple;
    char *str;
    mode_t mode;
    struct timespec tspec;
    struct list_members list;
};

/*
 * Structure describing compile-time and run-time options.
 */
struct sudo_defs_types {
    char *name;
    int type;
    char *desc;
    struct def_values *values;
    bool (*callback)(const union sudo_defs_val *);
    union sudo_defs_val sd_un;
};

struct def_values def_data_lecture[] = {
    { "never", never },
    { "once", once },
    { "always", always },
    { NULL, 0 },
};

struct def_values def_data_listpw[] = {
    { "never", never },
    { "any", any },
    { "all", all },
    { "always", always },
    { NULL, 0 },
};

struct def_values def_data_verifypw[] = {
    { "never", never },
    { "all", all },
    { "any", any },
    { "always", always },
    { NULL, 0 },
};

struct def_values def_data_fdexec[] = {
    { "never", never },
    { "digest_only", digest_only },
    { "always", always },
    { NULL, 0 },
};

struct def_values def_data_timestamp_type[] = {
    { "global", global },
    { "ppid", ppid },
    { "tty", tty },
    { "kernel", kernel },
    { NULL, 0 },
};

struct sudo_defs_types sudo_defs_table[] = {
    {
	"syslog", T_LOGFAC|T_BOOL,
	N_("Syslog facility if syslog is being used for logging: %s"),
	NULL,
    }, {
	"syslog_goodpri", T_LOGPRI|T_BOOL,
	N_("Syslog priority to use when user authenticates successfully: %s"),
	NULL,
    }, {
	"syslog_badpri", T_LOGPRI|T_BOOL,
	N_("Syslog priority to use when user authenticates unsuccessfully: %s"),
	NULL,
    }, {
	"long_otp_prompt", T_FLAG,
	N_("Put OTP prompt on its own line"),
	NULL,
    }, {
	"ignore_dot", T_FLAG,
	N_("Ignore '.' in $PATH"),
	NULL,
    }, {
	"mail_always", T_FLAG,
	N_("Always send mail when sudo is run"),
	NULL,
    }, {
	"mail_badpass", T_FLAG,
	N_("Send mail if user authentication fails"),
	NULL,
    }, {
	"mail_no_user", T_FLAG,
	N_("Send mail if the user is not in sudoers"),
	NULL,
    }, {
	"mail_no_host", T_FLAG,
	N_("Send mail if the user is not in sudoers for this host"),
	NULL,
    }, {
	"mail_no_perms", T_FLAG,
	N_("Send mail if the user is not allowed to run a command"),
	NULL,
    }, {
	"mail_all_cmnds", T_FLAG,
	N_("Send mail if the user tries to run a command"),
	NULL,
    }, {
	"tty_tickets", T_FLAG,
	N_("Use a separate timestamp for each user/tty combo"),
	NULL,
    }, {
	"lecture", T_TUPLE|T_BOOL,
	N_("Lecture user the first time they run sudo"),
	def_data_lecture,
    }, {
	"lecture_file", T_STR|T_PATH|T_BOOL,
	N_("File containing the sudo lecture: %s"),
	NULL,
    }, {
	"authenticate", T_FLAG,
	N_("Require users to authenticate by default"),
	NULL,
    }, {
	"root_sudo", T_FLAG,
	N_("Root may run sudo"),
	NULL,
    }, {
	"log_host", T_FLAG,
	N_("Log the hostname in the (non-syslog) log file"),
	NULL,
    }, {
	"log_year", T_FLAG,
	N_("Log the year in the (non-syslog) log file"),
	NULL,
    }, {
	"shell_noargs", T_FLAG,
	N_("If sudo is invoked with no arguments, start a shell"),
	NULL,
    }, {
	"set_home", T_FLAG,
	N_("Set $HOME to the target user when starting a shell with -s"),
	NULL,
    }, {
	"always_set_home", T_FLAG,
	N_("Always set $HOME to the target user's home directory"),
	NULL,
    }, {
	"path_info", T_FLAG,
	N_("Allow some information gathering to give useful error messages"),
	NULL,
    }, {
	"fqdn", T_FLAG,
	N_("Require fully-qualified hostnames in the sudoers file"),
	NULL,
    }, {
	"insults", T_FLAG,
	N_("Insult the user when they enter an incorrect password"),
	NULL,
    }, {
	"requiretty", T_FLAG,
	N_("Only allow the user to run sudo if they have a tty"),
	NULL,
    }, {
	"env_editor", T_FLAG,
	N_("Visudo will honor the EDITOR environment variable"),
	NULL,
    }, {
	"rootpw", T_FLAG,
	N_("Prompt for root's password, not the users's"),
	NULL,
    }, {
	"runaspw", T_FLAG,
	N_("Prompt for the runas_default user's password, not the users's"),
	NULL,
    }, {
	"targetpw", T_FLAG,
	N_("Prompt for the target user's password, not the users's"),
	NULL,
    }, {
	"use_loginclass", T_FLAG,
	N_("Apply defaults in the target user's login class if there is one"),
	NULL,
    }, {
	"set_logname", T_FLAG,
	N_("Set the LOGNAME and USER environment variables"),
	NULL,
    }, {
	"stay_setuid", T_FLAG,
	N_("Only set the effective uid to the target user, not the real uid"),
	NULL,
    }, {
	"preserve_groups", T_FLAG,
	N_("Don't initialize the group vector to that of the target user"),
	NULL,
    }, {
	"loglinelen", T_UINT|T_BOOL,
	N_("Length at which to wrap log file lines (0 for no wrap): %u"),
	NULL,
    }, {
	"timestamp_timeout", T_TIMESPEC|T_BOOL,
	N_("Authentication timestamp timeout: %.1f minutes"),
	NULL,
    }, {
	"passwd_timeout", T_TIMESPEC|T_BOOL,
	N_("Password prompt timeout: %.1f minutes"),
	NULL,
    }, {
	"passwd_tries", T_UINT,
	N_("Number of tries to enter a password: %u"),
	NULL,
    }, {
	"umask", T_MODE|T_BOOL,
	N_("Umask to use or 0777 to use user's: 0%o"),
	NULL,
    }, {
	"logfile", T_STR|T_BOOL|T_PATH,
	N_("Path to log file: %s"),
	NULL,
    }, {
	"mailerpath", T_STR|T_BOOL|T_PATH,
	N_("Path to mail program: %s"),
	NULL,
    }, {
	"mailerflags", T_STR|T_BOOL,
	N_("Flags for mail program: %s"),
	NULL,
    }, {
	"mailto", T_STR|T_BOOL,
	N_("Address to send mail to: %s"),
	NULL,
    }, {
	"mailfrom", T_STR|T_BOOL,
	N_("Address to send mail from: %s"),
	NULL,
    }, {
	"mailsub", T_STR,
	N_("Subject line for mail messages: %s"),
	NULL,
    }, {
	"badpass_message", T_STR,
	N_("Incorrect password message: %s"),
	NULL,
    }, {
	"lecture_status_dir", T_STR|T_PATH,
	N_("Path to lecture status dir: %s"),
	NULL,
    }, {
	"timestampdir", T_STR|T_PATH,
	N_("Path to authentication timestamp dir: %s"),
	NULL,
    }, {
	"timestampowner", T_STR,
	N_("Owner of the authentication timestamp dir: %s"),
	NULL,
    }, {
	"exempt_group", T_STR|T_BOOL,
	N_("Users in this group are exempt from password and PATH requirements: %s"),
	NULL,
    }, {
	"passprompt", T_STR,
	N_("Default password prompt: %s"),
	NULL,
    }, {
	"passprompt_override", T_FLAG,
	N_("If set, passprompt will override system prompt in all cases."),
	NULL,
    }, {
	"runas_default", T_STR,
	N_("Default user to run commands as: %s"),
	NULL,
    }, {
	"secure_path", T_STR|T_BOOL,
	N_("Value to override user's $PATH with: %s"),
	NULL,
    }, {
	"editor", T_STR|T_PATH,
	N_("Path to the editor for use by visudo: %s"),
	NULL,
    }, {
	"listpw", T_TUPLE|T_BOOL,
	N_("When to require a password for 'list' pseudocommand: %s"),
	def_data_listpw,
    }, {
	"verifypw", T_TUPLE|T_BOOL,
	N_("When to require a password for 'verify' pseudocommand: %s"),
	def_data_verifypw,
    }, {
	"noexec", T_FLAG,
	N_("Preload the dummy exec functions contained in the sudo_noexec library"),
	NULL,
    }, {
	"ignore_local_sudoers", T_FLAG,
	N_("If LDAP directory is up, do we ignore local sudoers file"),
	NULL,
    }, {
	"closefrom", T_INT,
	N_("File descriptors >= %d will be closed before executing a command"),
	NULL,
    }, {
	"closefrom_override", T_FLAG,
	N_("If set, users may override the value of `closefrom' with the -C option"),
	NULL,
    }, {
	"setenv", T_FLAG,
	N_("Allow users to set arbitrary environment variables"),
	NULL,
    }, {
	"env_reset", T_FLAG,
	N_("Reset the environment to a default set of variables"),
	NULL,
    }, {
	"env_check", T_LIST|T_BOOL,
	N_("Environment variables to check for sanity:"),
	NULL,
    }, {
	"env_delete", T_LIST|T_BOOL,
	N_("Environment variables to remove:"),
	NULL,
    }, {
	"env_keep", T_LIST|T_BOOL,
	N_("Environment variables to preserve:"),
	NULL,
    }, {
	"role", T_STR,
	N_("SELinux role to use in the new security context: %s"),
	NULL,
    }, {
	"type", T_STR,
	N_("SELinux type to use in the new security context: %s"),
	NULL,
    }, {
	"env_file", T_STR|T_PATH|T_BOOL,
	N_("Path to the sudo-specific environment file: %s"),
	NULL,
    }, {
	"restricted_env_file", T_STR|T_PATH|T_BOOL,
	N_("Path to the restricted sudo-specific environment file: %s"),
	NULL,
    }, {
	"sudoers_locale", T_STR,
	N_("Locale to use while parsing sudoers: %s"),
	NULL,
    }, {
	"visiblepw", T_FLAG,
	N_("Allow sudo to prompt for a password even if it would be visible"),
	NULL,
    }, {
	"pwfeedback", T_FLAG,
	N_("Provide visual feedback at the password prompt when there is user input"),
	NULL,
    }, {
	"fast_glob", T_FLAG,
	N_("Use faster globbing that is less accurate but does not access the filesystem"),
	NULL,
    }, {
	"umask_override", T_FLAG,
	N_("The umask specified in sudoers will override the user's, even if it is more permissive"),
	NULL,
    }, {
	"log_input", T_FLAG,
	N_("Log user's input for the command being run"),
	NULL,
    }, {
	"log_output", T_FLAG,
	N_("Log the output of the command being run"),
	NULL,
    }, {
	"compress_io", T_FLAG,
	N_("Compress I/O logs using zlib"),
	NULL,
    }, {
	"use_pty", T_FLAG,
	N_("Always run commands in a pseudo-tty"),
	NULL,
    }, {
	"group_plugin", T_STR,
	N_("Plugin for non-Unix group support: %s"),
	NULL,
    }, {
	"iolog_dir", T_STR|T_PATH,
	N_("Directory in which to store input/output logs: %s"),
	NULL,
    }, {
	"iolog_file", T_STR,
	N_("File in which to store the input/output log: %s"),
	NULL,
    }, {
	"set_utmp", T_FLAG,
	N_("Add an entry to the utmp/utmpx file when allocating a pty"),
	NULL,
    }, {
	"utmp_runas", T_FLAG,
	N_("Set the user in utmp to the runas user, not the invoking user"),
	NULL,
    }, {
	"privs", T_STR,
	N_("Set of permitted privileges: %s"),
	NULL,
    }, {
	"limitprivs", T_STR,
	N_("Set of limit privileges: %s"),
	NULL,
    }, {
	"exec_background", T_FLAG,
	N_("Run commands on a pty in the background"),
	NULL,
    }, {
	"pam_service", T_STR,
	N_("PAM service name to use: %s"),
	NULL,
    }, {
	"pam_login_service", T_STR,
	N_("PAM service name to use for login shells: %s"),
	NULL,
    }, {
	"pam_setcred", T_FLAG,
	N_("Attempt to establish PAM credentials for the target user"),
	NULL,
    }, {
	"pam_session", T_FLAG,
	N_("Create a new PAM session for the command to run in"),
	NULL,
    }, {
	"pam_acct_mgmt", T_FLAG,
	N_("Perform PAM account validation management"),
	NULL,
    }, {
	"maxseq", T_UINT,
	N_("Maximum I/O log sequence number: %u"),
	NULL,
    }, {
	"use_netgroups", T_FLAG,
	N_("Enable sudoers netgroup support"),
	NULL,
    }, {
	"sudoedit_checkdir", T_FLAG,
	N_("Check parent directories for writability when editing files with sudoedit"),
	NULL,
    }, {
	"sudoedit_follow", T_FLAG,
	N_("Follow symbolic links when editing files with sudoedit"),
	NULL,
    }, {
	"always_query_group_plugin", T_FLAG,
	N_("Query the group plugin for unknown system groups"),
	NULL,
    }, {
	"netgroup_tuple", T_FLAG,
	N_("Match netgroups based on the entire tuple: user, host and domain"),
	NULL,
    }, {
	"ignore_audit_errors", T_FLAG,
	N_("Allow commands to be run even if sudo cannot write to the audit log"),
	NULL,
    }, {
	"ignore_iolog_errors", T_FLAG,
	N_("Allow commands to be run even if sudo cannot write to the I/O log"),
	NULL,
    }, {
	"ignore_logfile_errors", T_FLAG,
	N_("Allow commands to be run even if sudo cannot write to the log file"),
	NULL,
    }, {
	"match_group_by_gid", T_FLAG,
	N_("Resolve groups in sudoers and match on the group ID, not the name"),
	NULL,
    }, {
	"syslog_maxlen", T_UINT,
	N_("Log entries larger than this value will be split into multiple syslog messages: %u"),
	NULL,
    }, {
	"iolog_user", T_STR|T_BOOL,
	N_("User that will own the I/O log files: %s"),
	NULL,
    }, {
	"iolog_group", T_STR|T_BOOL,
	N_("Group that will own the I/O log files: %s"),
	NULL,
    }, {
	"iolog_mode", T_MODE,
	N_("File mode to use for the I/O log files: 0%o"),
	NULL,
    }, {
	"fdexec", T_TUPLE|T_BOOL,
	N_("Execute commands by file descriptor instead of by path: %s"),
	def_data_fdexec,
    }, {
	"ignore_unknown_defaults", T_FLAG,
	N_("Ignore unknown Defaults entries in sudoers instead of producing a warning"),
	NULL,
    }, {
	"command_timeout", T_TIMEOUT|T_BOOL,
	N_("Time in seconds after which the command will be terminated: %u"),
	NULL,
    }, {
	"user_command_timeouts", T_FLAG,
	N_("Allow the user to specify a timeout on the command line"),
	NULL,
    }, {
	"iolog_flush", T_FLAG,
	N_("Flush I/O log data to disk immediately instead of buffering it"),
	NULL,
    }, {
	"syslog_pid", T_FLAG,
	N_("Include the process ID when logging via syslog"),
	NULL,
    }, {
	"timestamp_type", T_TUPLE,
	N_("Type of authentication timestamp record: %s"),
	def_data_timestamp_type,
    }, {
	"authfail_message", T_STR,
	N_("Authentication failure message: %s"),
	NULL,
    }, {
	"case_insensitive_user", T_FLAG,
	N_("Ignore case when matching user names"),
	NULL,
    }, {
	"case_insensitive_group", T_FLAG,
	N_("Ignore case when matching group names"),
	NULL,
    }, {
	"log_allowed", T_FLAG,
	N_("Log when a command is allowed by sudoers"),
	NULL,
    }, {
	"log_denied", T_FLAG,
	N_("Log when a command is denied by sudoers"),
	NULL,
    }, {
	"legacy_group_processing", T_FLAG,
	N_("Don't pre-resolve all group names"),
	NULL,
    }, {
	"cmnd_no_wait", T_FLAG,
	N_("Don't fork and wait for the command to finish, just exec it"),
	NULL,
    }, {
	"runas_allow_unknown_id", T_FLAG,
	N_("Allow the use of unknown runas user and/or group ID"),
	NULL,
    }, {
	"runas_check_shell", T_FLAG,
	N_("Only permit running commands as a user with a valid shell"),
	NULL,
    }, {
	NULL, 0, NULL
    }
};



/*
 * Values for sudoers_setlocale()
 */
#define SUDOERS_LOCALE_USER     0
#define SUDOERS_LOCALE_SUDOERS  1

#define	_PATH_DEVNULL	"/dev/null"

/* Special message for log_warning() so we know to use ngettext() */
#define INCORRECT_PASSWORD_ATTEMPT	((char *)0x01)
#define PERM_ROOT		0x01

#define	LOG_PID		0x01	/* log the pid with each message */

/* Flags for log_warning()/log_warningx() */
#define SLOG_USE_ERRNO		0x01	/* internal use only */
#define SLOG_GAI_ERRNO		0x02	/* internal use only */
#define SLOG_RAW_MSG		0x04	/* do not format msg before logging */
#define SLOG_SEND_MAIL		0x08	/* log via mail */
#define SLOG_NO_STDERR		0x10	/* do not log via stderr */
#define SLOG_NO_LOG		0x20	/* do not log via file or syslog */

#define	LOG_ERR		3	/* error conditions */

static const char *const sudoers_subsystem_names[] = {
    "alias",
    "audit",
    "auth",
    "defaults",
    "env",
    "event",
    "ldap",
    "logging",
    "main",
    "match",
    "netif",
    "nss",
    "parser",
    "perms",
    "plugin",
    "rbtree",
    "sssd",
    "util",
    NULL
};

#define NUM_SUBSYSTEMS  (nitems(sudoers_subsystem_names) - 1)

/* Subsystem IDs assigned at registration time. */
unsigned int sudoers_subsystem_ids[NUM_SUBSYSTEMS];

#define SUDOERS_DEBUG_AUDIT	(sudoers_subsystem_ids[ 1]) /* audit */
#define SUDOERS_DEBUG_LOGGING	(sudoers_subsystem_ids[ 7]) /* logging functions */


#define I_SYSLOG                0
#define def_syslog              (sudo_defs_table[I_SYSLOG].sd_un.ival)

#define I_SYSLOG_BADPRI         2
#define def_syslog_badpri       (sudo_defs_table[I_SYSLOG_BADPRI].sd_un.ival)

#define I_LOG_YEAR              17
#define def_log_year            (sudo_defs_table[I_LOG_YEAR].sd_un.flag)

#define I_LOGFILE               38
#define def_logfile             (sudo_defs_table[I_LOGFILE].sd_un.str)

#define I_MAILERPATH            39
#define def_mailerpath          (sudo_defs_table[I_MAILERPATH].sd_un.str)

#define I_MAILTO                41
#define def_mailto              (sudo_defs_table[I_MAILTO].sd_un.str)

#define I_MAILFROM              42
#define def_mailfrom            (sudo_defs_table[I_MAILFROM].sd_un.str)

#define I_MAILSUB               43
#define def_mailsub             (sudo_defs_table[I_MAILSUB].sd_un.str)

#define I_SUDOERS_LOCALE        69
#define def_sudoers_locale      (sudo_defs_table[I_SUDOERS_LOCALE].sd_un.str)

#define I_SYSLOG_PID            110
#define def_syslog_pid          (sudo_defs_table[I_SYSLOG_PID].sd_un.flag)

#define I_AUTHFAIL_MESSAGE      112
#define def_authfail_message    (sudo_defs_table[I_AUTHFAIL_MESSAGE].sd_un.str)

#define I_LOG_DENIED            116
#define def_log_denied          (sudo_defs_table[I_LOG_DENIED].sd_un.flag)


/*
 * Send a message to MAILTO user
 */
bool
send_mail(const char *fmt, ...)
{
    FILE *mail;
    char *p;
    const char *timestr;
    int fd, pfd[2], status;
    pid_t pid, rv;
    struct stat sb;
    va_list ap;
    debug_decl(send_mail, SUDOERS_DEBUG_LOGGING)

    /* If mailer is disabled just return. */
    if (!def_mailerpath || !def_mailto)
	debug_return_bool(true);

    /* Make sure the mailer exists and is a regular file. */
    if (stat(def_mailerpath, &sb) != 0 || !S_ISREG(sb.st_mode))
	debug_return_bool(false);

    /* Fork and return, child will daemonize. */
    switch (pid = sudo_debug_fork()) {
	case -1:
	    /* Error. */
	    sudo_warn(U_("unable to fork"));
	    debug_return_bool(false);
	    break;
	case 0:
	    /* Child. */
	    switch (pid = fork()) {
		case -1:
		    /* Error. */
		    mysyslog(LOG_ERR, _("unable to fork: %m"));
		    sudo_debug_printf(SUDO_DEBUG_ERROR, "unable to fork: %s",
			strerror(errno));
		    sudo_debug_exit(__func__, __FILE__, __LINE__, sudo_debug_subsys);
		    _exit(1);
		case 0:
		    /* Grandchild continues below. */
		    sudo_debug_enter(__func__, __FILE__, __LINE__, sudo_debug_subsys);
		    break;
		default:
		    /* Parent will wait for us. */
		    _exit(0);
	    }
	    break;
	default:
	    /* Parent. */
	    for (;;) {
		rv = waitpid(pid, &status, 0);
		if (rv == -1 && errno != EINTR)
		    break;
		if (rv != -1 && !WIFSTOPPED(status))
		    break;
	    }
	    sudo_debug_printf(SUDO_DEBUG_INFO|SUDO_DEBUG_LINENO,
		"child (%d) exit value %d", (int)rv, status);
	    debug_return_bool(true);
    }

    /* Daemonize - disassociate from session/tty. */
    if (setsid() == -1)
      sudo_warn("setsid");
    if (chdir("/") == -1)
      sudo_warn("chdir(/)");
    fd = open(_PATH_DEVNULL, O_RDWR, S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH);
    if (fd != -1) {
	(void) dup2(fd, STDIN_FILENO);
	(void) dup2(fd, STDOUT_FILENO);
	(void) dup2(fd, STDERR_FILENO);
    }

    sudoers_setlocale(SUDOERS_LOCALE_SUDOERS, NULL);

    /* Close non-debug fds so we don't leak anything. */
    closefrom_nodebug(STDERR_FILENO + 1);

    if (pipe(pfd) == -1) {
	mysyslog(LOG_ERR, _("unable to open pipe: %m"));
	sudo_debug_printf(SUDO_DEBUG_ERROR, "unable to open pipe: %s",
	    strerror(errno));
	sudo_debug_exit(__func__, __FILE__, __LINE__, sudo_debug_subsys);
	_exit(1);
    }

    switch (pid = sudo_debug_fork()) {
	case -1:
	    /* Error. */
	    mysyslog(LOG_ERR, _("unable to fork: %m"));
	    sudo_debug_printf(SUDO_DEBUG_ERROR, "unable to fork: %s",
		strerror(errno));
	    sudo_debug_exit(__func__, __FILE__, __LINE__, sudo_debug_subsys);
	    _exit(1);
	    break;
	case 0:
	    /* Child. */
	    exec_mailer(pfd);
	    /* NOTREACHED */
    }

    (void) close(pfd[0]);
    mail = fdopen(pfd[1], "w");

    /* Pipes are all setup, send message. */
    (void) fprintf(mail, "To: %s\nFrom: %s\nAuto-Submitted: %s\nSubject: ",
	def_mailto, def_mailfrom ? def_mailfrom : user_name, "auto-generated");
    for (p = _(def_mailsub); *p; p++) {
	/* Expand escapes in the subject */
	if (*p == '%' && *(p+1) != '%') {
	    switch (*(++p)) {
		case 'h':
		    (void) fputs(user_host, mail);
		    break;
		case 'u':
		    (void) fputs(user_name, mail);
		    break;
		default:
		    p--;
		    break;
	    }
	} else
	    (void) fputc(*p, mail);
    }

#ifdef HAVE_NL_LANGINFO
    if (strcmp(def_sudoers_locale, "C") != 0)
	(void) fprintf(mail, "\nContent-Type: text/plain; charset=\"%s\"\nContent-Transfer-Encoding: 8bit", nl_langinfo(CODESET));
#endif /* HAVE_NL_LANGINFO */

    if ((timestr = get_timestr(time(NULL), def_log_year)) == NULL)
	timestr = "invalid date";
    (void) fprintf(mail, "\n\n%s : %s : %s : ", user_host, timestr, sudo_user_name);
    va_start(ap, fmt);
    (void) vfprintf(mail, fmt, ap);
    va_end(ap);
    fputs("\n\n", mail);

    fclose(mail);
    for (;;) {
	rv = waitpid(pid, &status, 0);
	if (rv == -1 && errno != EINTR)
	    break;
	if (rv != -1 && !WIFSTOPPED(status))
	    break;
    }
    sudo_debug_printf(SUDO_DEBUG_INFO|SUDO_DEBUG_LINENO,
	"child (%d) exit value %d", (int)rv, status);
    sudo_debug_exit(__func__, __FILE__, __LINE__, sudo_debug_subsys);
    _exit(0);
}

/*
 * We do an openlog(3)/closelog(3) for each message because some
 * authentication methods (notably PAM) use syslog(3) for their
 * own nefarious purposes and may call openlog(3) and closelog(3).
 */
void
mysyslog(int pri, const char *fmt, ...)
{
    const int flags = def_syslog_pid ? LOG_PID : 0;
    va_list ap;
    debug_decl(mysyslog, SUDOERS_DEBUG_LOGGING)

    openlog("sudo", flags, def_syslog);
    va_start(ap, fmt);
    vsyslog(pri, fmt, ap);
    va_end(ap);
    closelog();
    debug_return;
}


/*
 * Format an authentication failure message, using either
 * authfail_message from sudoers or a locale-specific message.
 */
int
fmt_authfail_message(char **str, va_list ap)
{
    unsigned int tries = va_arg(ap, unsigned int);
    char *src, *dst0, *dst, *dst_end;
    size_t size;
    int len;
    debug_decl(fmt_authfail_message, SUDOERS_DEBUG_LOGGING)

    if (def_authfail_message == NULL) {
	debug_return_int(asprintf(str, ngettext("%u incorrect password attempt",
	    "%u incorrect password attempts", tries), tries));
    }

    src = def_authfail_message;
    size = strlen(src) + 33;
    if ((dst0 = dst = malloc(size)) == NULL)
	debug_return_int(-1);
    dst_end = dst + size;

    /* Always leave space for the terminating NUL. */
    while (*src != '\0' && dst + 1 < dst_end) {
	if (src[0] == '%') {
	    switch (src[1]) {
	    case '%':
		src++;
		break;
	    case 'd':
		len = snprintf(dst, dst_end - dst, "%u", tries);
		if (len < 0 || len >= (int)(dst_end - dst))
		    goto done;
		dst += len;
		src += 2;
		continue;
	    default:
		break;
	    }
	}
	*dst++ = *src++;
    }
done:
    *dst = '\0';

    *str = dst0;
#ifdef __clang_analyzer__
    /* clang analyzer false positive */
    if (__builtin_expect(dst < dst0, 0))
	__builtin_trap();
#endif
    debug_return_int(dst - dst0);
}

/*
 * Perform logging for log_warning()/log_warningx().
 */
bool
vlog_warning(int flags, int errnum, const char *fmt, va_list ap)
{
    int oldlocale;
    const char *errstr = NULL;
    char *logline, *message;
    bool uid_changed, ret = true;
    va_list ap2;
    int len;
    debug_decl(vlog_warning, SUDOERS_DEBUG_LOGGING)

    /* Need extra copy of ap for sudo_vwarn()/sudo_vwarnx() below. */
    va_copy(ap2, ap);

    /* Log messages should be in the sudoers locale. */
    sudoers_setlocale(SUDOERS_LOCALE_SUDOERS, &oldlocale);

    /* Expand printf-style format + args (with a special case). */
    if (fmt == INCORRECT_PASSWORD_ATTEMPT) {
	len = fmt_authfail_message(&message, ap);
    } else {
	len = vasprintf(&message, _(fmt), ap);
    }
    if (len == -1) {
	sudo_warnx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
	ret = false;
	goto done;
    }

    if (ISSET(flags, SLOG_USE_ERRNO))
	errstr = strerror(errnum);
    else if (ISSET(flags, SLOG_GAI_ERRNO))
	errstr = gai_strerror(errnum);

    /* Log to debug file. */
    if (errstr != NULL) {
	sudo_debug_printf2(NULL, NULL, 0,
	    SUDO_DEBUG_WARN|sudo_debug_subsys, "%s: %s", message, errstr);
    } else {
	sudo_debug_printf2(NULL, NULL, 0,
	    SUDO_DEBUG_WARN|sudo_debug_subsys, "%s", message);
    }

    if (ISSET(flags, SLOG_RAW_MSG)) {
	logline = message;
    } else {
	logline = new_logline(message, errstr);
        free(message);
	if (logline == NULL) {
	    ret = false;
	    sudo_warnx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
	    goto done;
	}
    }

    /* Become root if we are not already. */
    uid_changed = set_perms(PERM_ROOT);

    /*
     * Send a copy of the error via mail.
     * XXX - return value
     */
    if (ISSET(flags, SLOG_SEND_MAIL))
	send_mail("%s", logline);

    /*
     * Log to syslog and/or a file.
     */
    if (!ISSET(flags, SLOG_NO_LOG)) {
	if (def_syslog)
	    do_syslog(def_syslog_badpri, logline);
	if (def_logfile && !do_logfile(logline))
	    ret = false;
    }

    if (uid_changed) {
	if (!restore_perms())
	    ret = false;
    }

    free(logline);

    /*
     * Tell the user (in their locale).
     */
    if (!ISSET(flags, SLOG_NO_STDERR)) {
	sudoers_setlocale(SUDOERS_LOCALE_USER, NULL);
	if (fmt == INCORRECT_PASSWORD_ATTEMPT) {
	    len = fmt_authfail_message(&message, ap2);
	    if (len == -1) {
		sudo_warnx(U_("%s: %s"), __func__,
		    U_("unable to allocate memory"));
		ret = false;
		goto done;
	    }
	    sudo_warnx_nodebug("%s", message);
	    free(message);
	} else {
	    if (ISSET(flags, SLOG_USE_ERRNO)) {
		errno = errnum;
		sudo_vwarn_nodebug(_(fmt), ap2);
	    } else if (ISSET(flags, SLOG_GAI_ERRNO)) {
		sudo_gai_vwarn_nodebug(errnum, _(fmt), ap2);
	    } else
		sudo_vwarnx_nodebug(_(fmt), ap2);
	}
    }

done:
    va_end(ap2);
    sudoers_setlocale(oldlocale, NULL);

    debug_return_bool(ret);
}


bool
log_warning(int flags, const char *fmt, ...)
{
    va_list ap;
    bool ret;
    debug_decl(log_warning, SUDOERS_DEBUG_LOGGING)

    /* Log the error. */
    va_start(ap, fmt);
    ret = vlog_warning(flags|SLOG_USE_ERRNO, errno, fmt, ap);
    va_end(ap);

    debug_return_bool(ret);
}

bool
log_warningx(int flags, const char *fmt, ...)
{
    va_list ap;
    bool ret;
    debug_decl(log_warningx, SUDOERS_DEBUG_LOGGING)

    /* Log the error. */
    va_start(ap, fmt);
    ret = vlog_warning(flags, 0, fmt, ap);
    va_end(ap);

    debug_return_bool(ret);
}

bool
gai_log_warning(int flags, int errnum, const char *fmt, ...)
{
    va_list ap;
    bool ret;
    debug_decl(gai_log_warning, SUDOERS_DEBUG_LOGGING)

    /* Log the error. */
    va_start(ap, fmt);
    ret = vlog_warning(flags|SLOG_GAI_ERRNO, errnum, fmt, ap);
    va_end(ap);

    debug_return_bool(ret);
}


int
sudo_printf_int(int msg_type, const char *fmt, ...)
{
    FILE *fp = stdout;
    FILE *ttyfp = NULL;
    va_list ap;
    int len;

    if (ISSET(msg_type, SUDO_CONV_PREFER_TTY)) {
	/* Try writing to /dev/tty first. */
	ttyfp = fopen(_PATH_TTY, "w");
    }

    switch (msg_type & 0xff) {
    case SUDO_CONV_ERROR_MSG:
	fp = stderr;
	/* FALLTHROUGH */
    case SUDO_CONV_INFO_MSG:
	va_start(ap, fmt);
	len = vfprintf(ttyfp ? ttyfp : fp, fmt, ap);
	va_end(ap);
	break;
    default:
	len = -1;
	errno = EINVAL;
	break;
    }

    if (ttyfp != NULL)
	fclose(ttyfp);

    return len;
}

int
audit_failure(int argc, char *argv[], char const *const fmt, ...)
{
    int rc = 0;
    debug_decl(audit_success, SUDOERS_DEBUG_AUDIT)

    if (!def_log_denied)
	debug_return_int(0);

#if defined(HAVE_BSM_AUDIT) || defined(HAVE_LINUX_AUDIT)
    if (argv != NULL) {
	va_list ap;
	int oldlocale;

	/* Audit error messages should be in the sudoers locale. */
	sudoers_setlocale(SUDOERS_LOCALE_SUDOERS, &oldlocale);

#ifdef HAVE_BSM_AUDIT
	va_start(ap, fmt);
	if (bsm_audit_failure(argv, _(fmt), ap) == -1)
	    rc = -1;
	va_end(ap);
#endif
#ifdef HAVE_LINUX_AUDIT
	va_start(ap, fmt);
	if (linux_audit_command(argv, 0) == -1)
	    rc = -1;
	va_end(ap);
#endif
#ifdef HAVE_SOLARIS_AUDIT
	va_start(ap, fmt);
	if (solaris_audit_failure(argc, argv, _(fmt), ap) == -1)
	    rc = -1;
	va_end(ap);
#endif

	sudoers_setlocale(oldlocale, NULL);
    }
#endif /* HAVE_BSM_AUDIT || HAVE_LINUX_AUDIT */

    debug_return_int(rc);
}
